package jhavelogger.visitor;

import java.sql.Date;
import java.sql.SQLException;
import java.util.Dictionary;
import java.util.Hashtable;

import com.maxmind.geoip.Location;

import jhavelogger.data.IPLookup;
import jhavelogger.data.LocationCache;
import jhavelogger.data.LogDAO;
import jhavelogger.data.GeoIP;
import jhavelogger.models.AcceptedConnection;
import jhavelogger.models.UsernameLookup;
import jhavelogger.models.DBLog;
import jhavelogger.models.JHaveLog;
import jhavelogger.models.SendAlgorithmToClient;
import jhavelogger.models.UidOperation;
import jhavelogger.visitor.logInterfaces.VisitsAcceptedConnection;
import jhavelogger.visitor.logInterfaces.VisitsAlgorithmLookup;
import jhavelogger.visitor.logInterfaces.VisitsSendAlgorithmToClient;
import jhavelogger.visitor.logInterfaces.VisitsUidOperation;

/**
 * Logs visited logs to a database
 * @author Jared Schlicht
 */
public class DBLogVisitor extends LogVisitor implements VisitsAcceptedConnection, VisitsAlgorithmLookup, VisitsUidOperation, VisitsSendAlgorithmToClient {
    /**
     * Maps the server that sent the log to a dictionary that maps a user's ID to their IP Address
     */
    private Dictionary<String,Dictionary<Integer,String>> acceptedConnectionsBySource;
    
    /**
     * Maps the server that sent the log to the last UID it has encountered
     */
    private Dictionary<String,Integer> lastUidOperationBySource;
    
    /**
     * Maps the server that sent the log to the last username it has encountered
     */
    private Dictionary<String,String> lastUsernameBySource;
    
    /**
     * Uses to send logs to the database
     */
    private LogDAO dao;
    
    /**
     * Used to lookup location information based on the user's IP address
     */
    private IPLookup ipLookup;
    
    /**
     * Used to cache frequently visited locations in memory.
     */
    private LocationCache cache;
    
    /**
     * Constructs a new DBLogVisitor object
     * @param dao the LogDAO object to use to connect to the database
     * @param ipLookup the IPLookup class to use to lookup location information
     */
    public DBLogVisitor(LogDAO dao, IPLookup ipLookup) {
	this.acceptedConnectionsBySource = new Hashtable<String,Dictionary<Integer, String>>();
	this.lastUidOperationBySource = new Hashtable<String,Integer>();
	this.lastUsernameBySource = new Hashtable<String,String>();
	this.dao = dao;
	this.ipLookup = ipLookup;
	this.cache = new LocationCache(2000);
    }

    public void setLastUsernameBySource(String source, String username) {
	this.lastUsernameBySource.put(source, username);
    }
    
    public String getLastUsernameBySource(String source) {
	return this.lastUsernameBySource.get(source);
    }
    
    @Override
    public Object defaultVisit(JHaveLog log) {
	return null;
    }

    public Dictionary<Integer, String> getAcceptedConnections(String source) {
        return this.acceptedConnectionsBySource.get(source);
    }

    public int getLastUidOperation(String source) {
	Integer result = this.lastUidOperationBySource.get(source);
	if (result != null) {
	    return result;
	} else {
	    return -1;
	}
    }
    
    protected void setAcceptedConnections(String source, Dictionary<Integer, String> acceptedConnections) {
        this.acceptedConnectionsBySource.put(source, acceptedConnections);
    }
    
    protected void setLastUidOperation(String source, int lastUidOperation) {
        this.lastUidOperationBySource.put(source, lastUidOperation);
    }

    /**
     * Keeps track of the user's uid and IP address.
     */
    @Override
    public Object visit(AcceptedConnection log) {
	Dictionary<Integer,String> acceptedConnections = this.getAcceptedConnections(log.getLogSource());
	if (acceptedConnections == null) {
	    acceptedConnections = new Hashtable<Integer,String>();
	    this.setAcceptedConnections(log.getLogSource(), acceptedConnections);
	}
	
	acceptedConnections.put(log.getUid(), log.getIpAddress());
	return null;
    }

    /**
     * Keeps track of the last UID the server encountered
     */
    @Override
    public Object visit(UidOperation log) {
	this.setLastUidOperation(log.getLogSource(), log.getUid());
	return null;
    }
    
    /**
     * Keeps track of the last username the server encountered
     */
    @Override
    public Object visit(UsernameLookup log) {
	this.setLastUsernameBySource(log.getLogSource(), log.getUsername());
	return null;
    }

    /**
     * At this point, the server has enough information to save a DBLog to the
     * database. Uses the last uid and username that the server encountered.
     * This process involves looking up the user's location information.
     */
    @Override
    public Object visit(SendAlgorithmToClient log) {	
	int uid = this.getLastUidOperation(log.getLogSource());
	if (uid <= -1) {
	    return null;
	}
	
	String ip = this.getAcceptedConnections(log.getLogSource()).get(uid);
	if (ip == null) {
	    return null;
	}
	
	String username = this.getLastUsernameBySource(log.getLogSource());
	if (username == null) {
	    return null;
	}
	
	// Attempt to cache location information to reduce load on the GeoIP servers
	Location location = this.cache.get(ip);
	if (location == null) {
	    location = this.ipLookup.getIpLocation(ip);
	    this.cache.put(ip, location);
	}
	
	DBLog dbLog = new DBLog(username, ip, log.getAlgorithm(), log.getDate(), location.city, location.region, location.countryName);
	
	try {
	    this.dao.insertLog(dbLog);
	    this.dao.commit();
	} catch (SQLException e) {
	    e.printStackTrace();
	    return e.toString();
	}
	
	return dbLog;
    }
}
